/*	
 *				
 *					ISNW ---- It is a war.It is not warcraft!
 *
 *
 *					Author:					zhoukeli(haxixi_keli)
 *					Filename:				isnwMath.h
 *					CreateTime:				2008/12/06
 *					ModifyTime:				2008/12/06
 *					ModifyTime:				2008/12/08
 *
 */

#ifndef __ISNW_MATH_H__
#define __ISNW_MATH_H__

#include <math.h>
#include <float.h>
#include "isnwTypes.h"

namespace ISNW
{
namespace core
{
	const f32 _pi_ = 3.141592653589793238462;

	const f32 _infinit_f32_ = FLT_MAX;

	const s32 _infinit_s32_ = 0x7fffffff;

	const u32 _infinit_u32_ = 0xffffffff;

	const f32 _f32_tolerant_error_ = 0.0002f;

	const f64 _f64_tolerant_error_ = 0.000001f;

	const u32 _max_loop_times_ = 100;

	template<typename T>
	inline const T& _min_(const T& a,const T& b)
	{
		return (a <= b ? a : b);
	}

	template<typename T>
	inline const T& _min_(const T& a,const T& b,const T& c)
	{
		return (a <= b ? _min_(a,c) : _min_(b,c));
	}

	template<typename T>
	inline const T& _max_(const T& a,const T& b)
	{
		return (a >= b ? a : b);
	}

	template<typename T>
	inline const T& _max_(const T& a,const T& b,const T& c)
	{
		return (a >= b ? _max_(a,c) : _max_(b,c));
	}

	template<typename T>
	inline const T& _abs_(const T& x)
	{
		return (x >= 0 ? x : -x);
	}

	template<typename T>
	inline const T _clamp_(const T& x,const T& min,const T& max)
	{
		return (_max_(min,_min_(x,max)));
	}

	inline bool _equal_(const f32& a,const f32& b)
	{
		return (_abs_<f32>(a-b) <= _f32_tolerant_error_);
	}

	inline bool _equal_(const f64& a,const f64& b)
	{
		return (_abs_<f64>(a-b) <= _f64_tolerant_error_);
	}

	inline bool _equal_(const s32& a,const s32& b)
	{
		return (a == b);
	}

	inline bool _equal_(const u32& a,const u32& b)
	{
		return (a == b);
	}

	inline bool _equal_(const s16& a,const s16& b)
	{
		return (a == b);
	}

	inline bool _equal_(const u16& a,const u16& b)
	{
		return (a == b);
	}

	inline f32 _radio_2_angle_(const f32 radio)
	{
		return (radio * 180.0f / _pi_);
	}

	inline f32 _angle_2_radio_(const f32 angle)
	{
		return (angle * _pi_ / 180.0f);
	}

#define  _loop_8_(x,n)						\
	{										\
		u32 c = n / 8 + 1;					\
		switch(n % 8)						\
			do{								\
						x;				\
				case 7:	x;				\
				case 6:	x;				\
				case 5:	x;				\
				case 4:	x;				\
				case 3:	x;				\
				case 2:	x;				\
				case 1:	x;				\
				case 0:		 ;				\
			}while(--c);					\
	}

	inline f64 _factorial_(const u32 n)
	{
		f64 ret = 1;
		u32 num = n;
		_loop_8_(ret *= num--,n);
		return ret;
	}

	inline f64 _exponent_(const f32 num,const u32 exp)
	{
		f64 ret = 1;
		_loop_8_(ret *= num,exp);
		return ret;
	}

	//! Taylor expansion
	//! sin(x) = E<n=0,n=...>(-1)^(n)(x)^(2n+1)/(2n+1)!
	const static u32 _max_n_ = 20;
	inline f32 _sin_(const f32 a)
	{
		// -180 ~ +180
		f32 angle = a;
		while(angle < -180){angle += 360;}
		while(angle > 180){angle -= 360;}

		f32 radio = _angle_2_radio_(angle);
		f32 ret = 0;
		u32 n = 0;
		s32 sign = 1;
		while(n <= _max_n_)
		{
			ret += sign * _exponent_(radio,2*n+1)/_factorial_(2*n+1);
			sign *= -1;
			++n;
		}
		return ret;
	}

	//! Taylor expansion
	//! cos(x) = 1 + E<n=1,n=...>(-1)^(n)(x)^(2n)/(2n)!
	inline f32 _cos_(f32 a)
	{
		// 0 ~ 360
		f32 angle = a;
		while(angle < 0){angle += 360;}
		while(angle > 360){angle -= 360;}

		f32 radio = _angle_2_radio_(angle);
		f32 ret = 1;
		u32 n = 1;
		s32 sign = -1;
		while(n <= _max_n_)
		{
			ret += sign * _exponent_(radio,2*n) / _factorial_(2*n);
			sign *= -1;
			++n;
		}
		return ret;
	}

	inline f32 _tan_(f32 angle)
	{
		f32 cos = _cos_(angle);
		f32 ret = _infinit_f32_;
		if(cos)
			ret = _sin_(angle)/cos;
		return ret;
	}

	inline f32 _ctan_(f32 angle)
	{
		f32 sin = _sin_(angle);
		f32 ret = _infinit_f32_;
		if(sin)
			ret = _cos_(angle)/sin;
		return ret;
	}

	inline f32 _arcsin_(f32 value)
	{
		if(value < -1 || value > 1 || value == 0)
			return 0;
		if(value == 1)
			return 90.0f;
		if(value == -1)
			return -90.0f;
		f32 low = -90.0f;
		f32 top = 90.0f;
		f32 mid = (low + top)/2.0f;
		f32 sin_mid = _sin_(mid);

		u32 loop_time = 0;
		while(!_equal_(sin_mid,value) && !_equal_(low,top) && loop_time++ < _max_loop_times_)
		{
			if(sin_mid > value)
			{
				top = mid;
				mid = (low + top)/2.0f;
			}
			else if(sin_mid < value)
			{
				low = mid;
				mid = (low + top)/2.0f;
			}
			else if(sin_mid == value)
				break;
			sin_mid = _sin_(mid);
		}
		return mid;
	}

	inline f32 _arccos_(f32 value)
	{
		f32 arcsin = _arcsin_(value);
		return (90 - arcsin);
	}

	inline f32 _arctan_(f32 value)
	{
		if(value == _infinit_f32_) return 90;
		if(value == -_infinit_f32_)	return -90;
		if(value == 0) return 0;

		f32 low = -90;
		f32 top = 90;
		f32 mid = (low + top) / 2.0f;
		f32 tan_mid = _tan_(mid);
		while(!_equal_(tan_mid,value) && !_equal_(low,top))
		{
			if(tan_mid > value)
			{
				top = mid;
				mid = (low + top) / 2.0f;
			}
			else if(tan_mid < value)
			{
				low = mid;
				mid = (low + top) / 2.0f;
			}
			else if(tan_mid == value) break;

			tan_mid = _tan_(mid);
		}

		return mid;
	}

	inline f32 _arcctan_(f32 value)
	{
		if(value == _infinit_f32_) return 0;
		if(value == _infinit_f32_) return 180;
		if(value == 0) return 90;

		f32 tan_value = 1.0f / value;

		return (_arctan_(tan_value));
	}

	inline f32 _sqrt_(f32 num)
	{
		if(num <= 0)
			return 0;

		f32 low = 0;
		f32 top = 0;
		if(num < 100)
		{
			low = 0;
			top = 10;
		}
		else if(num < 10000)
		{
			low = 10;
			top = 100;
		}
		else if(num < 1000000)
		{
			low = 100;
			top = 1000;
		}
		else if(num < 100000000)
		{
			// big enough :)
			low = 1000;
			top = 10000;
		}
		f32 mid = (low + top)/2.0f;
		f32 square_mid = mid * mid;
		u32 loop_time = 0;
		while(!_equal_(square_mid,num) && !_equal_(low,top) && loop_time++ < _max_loop_times_)
		{
			if(square_mid > num)
			{
				top = mid;
				mid = (low + top)/2.0f;
			}
			else if(square_mid < num)
			{
				low = mid;
				mid = (low + top)/2.0f;
			}
			else if(square_mid == num) break;

			square_mid = mid * mid;
		}

		return mid;
	}

} // end namespace core
} // end namespace ISNW
#endif